/*
 * Copyright (c) 2002-2008 Universidade Federal de Campina Grande This program
 * is free software; you can redistribute it and/or modify it under the terms of
 * the GNU General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or (at your option) any later version. This
 * program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.ourgrid.worker.collectors.gmond;


import java.lang.reflect.Field;
import java.util.Properties;

import org.ourgrid.worker.collectors.common.Metric;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * An abstraction of a metric.
 * @author Guilherme Mauro Germoglio - guiga@ourgrid.org
 */
public class GMondMetric extends Metric {

	private static final long serialVersionUID = 1L;

	private static final String METRIC_ELEMENT_NAME = "METRIC";

	private static final String NAME_ATTRIBUTE = "NAME";

	private static final String CLUSTER_ELEMENT_NAME = "CLUSTER";
	private static final String HOST_ELEMENT_NAME = "HOST";
	
	private static final String PREFIX_PROP = "PROP_";
	
	public static final String PROP_NAME = "NAME";
	public static final String PROP_VAL = "VAL";
	public static final String PROP_TYPE = "TYPE";
	public static final String PROP_UNITS = "UNITS";
	public static final String PROP_SOURCE = "SOURCE";
	public static final String PROP_SLOPE = "SLOPE";
	public static final String PROP_TN = "TN";
	public static final String PROP_TMAX = "TMAX";
	public static final String PROP_DMAX = "DMAX";

	
	private final String tn;
	private final String type;
	private final String unit;
	private final String host;
	private final String cluster;
	private final String tmax;
	private final String dmax;
	private final String slope;
	private final String source;

	/**
	 * Creates a <code>Metric</code>.
	 * @param name Its name.
	 * @param value Its value.
	 * @param type Its type.
	 * @param unit Its unit.
	 * @param host Its host.
	 * @param cluster Its cluster.
	 * @param source Its source.
	 * @param tn Its tn.
	 * @param tmax Its tmax.
	 * @param dmax Its dmax.
	 * @param slope Its sloped.
	 */
	private GMondMetric(String name, String value, String type, String unit,
					String host, String cluster, String source, String tn,
					String tmax, String dmax, String slope) {
		super(name, value);
		this.type = type;
		this.unit = unit;
		this.host = host;
		this.cluster = cluster;
		this.source = source;
		this.tn = tn;
		this.tmax = tmax;
		this.dmax = dmax;
		this.slope = slope;
	}
	
	/**
	 * Its type.
	 * @return Its type.
	 */
	public String getType() {
		return this.type;
	}

	/**
	 * Its unit.
	 * @return Its unit.
	 */
	public String getUnit() {
		return this.unit;
	}

	/**
	 * The host whose this metric belongs to.
	 * @return The host whose this metric belongs to.
	 */
	public String getHost() {
		return this.host;
	}
	
	/**
	 * The cluster whose host belongs to.
	 * @return The cluster whose host belongs to.
	 */
	public String getCluster() {
		return this.cluster;
	}
	
	/**
	 * The Ganglia's TN value.
	 * @return The Ganglia's TN value.
	 */
	public String getTN() {
		return this.tn;
	}

	/**
	 * The Ganglia's TMax value.
	 * @return The Ganglia's TMax value.
	 */
	public String getTMax() {
		return this.tmax;
	}
	
	/**
	 * The Ganglia's DMax value.
	 * @return The Ganglia's DMax value.
	 */
	public String getDMax() {
		return this.dmax;
	}
	
	/**
	 * The metric's slope.
	 * @return The metric's slope.
	 */
	public String getSlope() {
		return this.slope;
	}
	
	/**
	 * The metric's source.
	 * @return The metric's source.
	 */
	public String getSource() {
		return this.source;
	}
	
	/**
	 * Creates a <code>Metric</code> given the <code>Node</code> with its values.
	 * @param node The <code>Node</code> that must contain its values.
	 * @return The <code>Metric</code>.
	 * @throws IllegalArgumentException If the <code>Node</code> specified is somehow invalid to generate
	 * a <code>Metric</code> (not a GMond's response METRIC node).
	 */
	public static GMondMetric create(Node node) {
		Node hostNode = node.getParentNode();
		
		String hostName = checkHostNode(hostNode);
		
		Node clusterNode = hostNode.getParentNode();
		
		String clusterName = checkClusterNode(clusterNode);
		
		Properties metricProperties = getMetricProperties(node);
		
		return new GMondMetric(metricProperties.getProperty(PROP_NAME), metricProperties.getProperty(PROP_VAL), metricProperties.getProperty(PROP_TYPE), metricProperties.getProperty(PROP_UNITS), hostName, clusterName, metricProperties.getProperty(PROP_SOURCE), metricProperties.getProperty(PROP_TN), metricProperties.getProperty(PROP_TMAX), metricProperties.getProperty(PROP_DMAX), metricProperties.getProperty(PROP_SLOPE));
	}

	/**
	 * Returns the metric's values from a <code>Node</code>.
	 * @param node The <code>Node</code> that must contain <code>Metric</code> attributes.
	 * @return A <code>Properties</code> object with the values needed.
	 * @throws IllegalArgumentException If the <code>Node</code> specified is invalid (not a METRIC node).
	 */
	private static Properties getMetricProperties(Node node) {
		
		if (node.getNodeType() != Node.ELEMENT_NODE) throw new IllegalArgumentException();
		
		Element element = (Element)node;
		
		if (!METRIC_ELEMENT_NAME.equals(element.getNodeName())) throw new IllegalArgumentException();
		
		Field[] fields = GMondMetric.class.getDeclaredFields();
		
		Properties properties = new Properties();
		
		for (Field field : fields) {
			
			String fieldName = field.getName();
			
			if (fieldName.startsWith(PREFIX_PROP)) {
				String metricName;
				try {
					metricName = field.get(null).toString();
				} catch (IllegalAccessException e) {
					throw new IllegalArgumentException(e);
				}
			
				
				if (element.hasAttribute(metricName)) {
					properties.put(metricName, element.getAttribute(metricName));
				} else {
					throw new IllegalArgumentException();
				}
				
			}
			
		}
		
		return properties;
	}

	/**
	 * Checks if the <code>Node</code> specified is a CLUSTER node.
	 * @param clusterNode The <code>Node</code> to be checked.
	 * @return The cluster name.
 	 * @throws IllegalArgumentException If the <code>Node</code> is <code>null</code>, if is not an <code>Element</code>, if its
	 * name is not <code>CLUSTER_ELEMENT_NAME</code> or if no <code>NAME_ATTRIBUTE</code> attribute is found.
	 */
	private static String checkClusterNode(Node clusterNode) {
		return checkNode(clusterNode, CLUSTER_ELEMENT_NAME, NAME_ATTRIBUTE);
	}

	/**
	 * Checks whether the <code>Node</code> is a valid <code>HOST</code> node.
	 * @param hostNode The <code>Node</code> that must represent the host.
	 * @return The <code>NAME</code> attribute of the <code>Node</code>.
 	 * @throws IllegalArgumentException If the <code>Node</code> is <code>null</code>, if is not an <code>Element</code>, if its
	 * name is not <code>HOST_ELEMENT_NAME</code> or if no <code>NAME_ATTRIBUTE</code> attribute is found.
	 */
	private static String checkHostNode(Node hostNode) {
		return checkNode(hostNode, HOST_ELEMENT_NAME, NAME_ATTRIBUTE);
	}

	/**
	 * Checks whether a <code>Node</code> is an <code>Element</code>, if it has the specified <code>elementName</code> and
	 * an attribute <code>attributeName</code>. 
	 * @param node The <code>Node</code> to be checked.
	 * @param elementName The name expected.
	 * @param attributeName The attribute wanted.
	 * @return The <code>attributeName</code> <code>Element</code>'s attribute value.
	 * @throws IllegalArgumentException If the <code>Node</code> is <code>null</code>, if is not an <code>Element</code>, if its
	 * name is not <code>elementName</code> or if no <code>attributeName</code> attribute is found.
	 */
	private static String checkNode(Node node, String elementName, String attributeName) {
		checkNodeNotNull(node);
		
		if (node.getNodeType() == Node.ELEMENT_NODE) {
			Element element = (Element)node;
			if (elementName.equals(element.getNodeName()) &&  element.hasAttribute(attributeName)) {
				return element.getAttribute(attributeName);
			}
		}
				
		throw new IllegalArgumentException();
	}

	/**
	 * Checks if a <code>Node</code> is <code>null</code> or not. If so, throws an <code>IllegalArgumentException</code>.
	 * @param node The <code>Node</code> to be checked.
	 * @throws IllegalArgumentException If the <code>Node</code> is null.
	 */
	private static void checkNodeNotNull(Node node) {
		if (node == null) throw new IllegalArgumentException();
		
	}
	
	/**
	 * Returns the String representation.
	 * @see Object#toString()
	 */
	public String toString() {
		return "[name="+getName()+
				",value="+getValue()+
				",unit="+this.unit+
				",type="+this.type+
				",host="+this.host+
				",cluster="+this.cluster+
				",dmax="+this.dmax+
				",tmax="+this.tmax+
				",tn="+this.tn+
				",slope="+this.slope+
				",source="+this.source+
				"]";
	}
	
	/**
	 * @see Object#equals(java.lang.Object)
	 */
	public boolean equals(Object o) {
		if (o == null) return false;
		
		if (!(o instanceof GMondMetric)) return false;
		
		GMondMetric other = (GMondMetric)o;
		
		return this.getName().equals(other.getName()) && 
				this.getValue().equals(other.getValue()) &&
				this.unit.equals(other.unit) &&
				this.type.equals(other.type) &&
				this.host.equals(other.host) &&
				this.cluster.equals(other.cluster) &&
				this.source.equals(other.source) &&
				this.slope.equals(other.slope);
	}
}
